<?php

use \Core\Log;
use \Core\Func;

/**
 * Description of run
 *
 * @author Administrator
 */
class Server
{

	public static $_masterSocket = null;
	public static $config = [];
	public static $class_obj = [];

	private static function init()
	{
		error_reporting(E_ALL);
		define('APP_PATH', __DIR__);


		//自动加载
		spl_autoload_register(function($name) {
			$class_path = __DIR__ . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.class.php';
			if (!is_file($class_path))
				die('文件' . $class_path . '未找到');
			if (!isset(self::$class_obj[$class_path])) {
				self::$class_obj[$class_path] = $class_path;
				include $class_path;
			}
		});

		//加载运行配置
		self::$config = \Core\Config::getInstance()->get();

		//运行环境检查
		\Core\CheckEnv::check();
	}

	public static function start()
	{
		//服务初始化
		self::init();

		//命令解析
		self::_parseCommand();
	}

	/**
	 * 解析启动命令,比如start, stop等 执行不同的操作
	 */
	private static function _parseCommand()
	{
		global $argv;

		$startFilename = $argv[0];
		if (!isset($argv[1]) || $argv[1] == '')
			die("Usage: php " . $startFilename . " start|stop|restart|status \n");

		$operation = strtolower(trim($argv[1]));
		//获取主进程ID - 用来判断当前进程是否在运行
		$masterPid = false;
		$pid_file = self::$config['common']['master_pid_file'];
		if (file_exists($pid_file))
			$masterPid = file_get_contents($pid_file);


		//主进程当前是否正在运行
		$master_is_alive = self::checkMasterIsAlive();
		$slave_is_alive = self::checkSlaveIsAlive();

		//给Service主进程发送一个信号, 信号为SIG_DFL, 表示采用默认信号处理程序.如果发送信号成功则该进程正常
		//启动不能重复启动
		if (($master_is_alive || $slave_is_alive) && $operation === 'start') {
			Log::write('Service is already running', 'FATAL');
			Func::echoColorText("Service is already running", 1);
			exit(255);
		}
		//未启动不能终止
		if ((!$master_is_alive && !$slave_is_alive) && $operation === 'stop') {
			Log::write('Service is not running', 'FATAL');
			Func::echoColorText("Service is not running", 1);
			exit(255);
		}

		//根据不同的执行参数执行不同的动作
		switch ($operation) {
			//启动
			case 'start':
				self::_commandStart();
				break;
			//停止
			case 'stop':
				self::_commandStop();
				break;
			//重启
			case 'restart':
				self::_commandRestart();
				break;
			//状态
			case 'status':
				self::_commandStatus();
				break;
			//参数不合法
			default:
				Log::write('Parameter error. Usage: php ' . $startFilename . ' start|stop|restart|status', 'FATAL');
				echo "Usage: php " . $startFilename . " start|stop|restart|status \n";
		}
	}

	/**
	 * 启动服务
	 */
	private static function _commandStart()
	{
		Log::write('Service start');

		/**
		 * 详见 http://php.net/manual/zh/context.ssl.php
		 */
		$context_option = [
			'ssl' => [
				'local_cert' => self::$config['http']['ssl_local_cert'], // 也可以是crt文件
				'local_pk' => self::$config['http']['ssl_local_pk'],
				'verify_peer' => false,
			],
			'socket' => ['backlog' => 102400]
		];

		//端口监听
		$errno = 0;
		$errmsg = '';
		$server_config = self::$config['http']['transport'] . '://' . self::$config['http']['lesten_ip'] . ':' . self::$config['http']['lesten_port'];
		if (self::$config['http']['lesten_port'] == '443') {
			$_context = stream_context_create($context_option);
			self::$_masterSocket = stream_socket_server($server_config, $errno, $errmsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $_context);
			stream_socket_enable_crypto(self::$_masterSocket, false);
		} else
			self::$_masterSocket = stream_socket_server($server_config, $errno, $errmsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);

		if (!self::$_masterSocket) {
			Log::write('stream_socket_server() error: errno=' . $errno . ' errmsg=' . $errmsg, 'FATAL');
			exit(1);
		}
		//如果是TCP协议,打开长链接,并且禁用Nagle算法,默认为开启Nagle
		//Nagle是收集多个数据包一起发送.再实时交互场景(比如游戏)中,追求高实时性,要求一个包,哪怕再小,也要立即发送给服务端.因此我们禁用Nagle
		if (function_exists('socket_import_stream') && self::$config['http']['lesten_port'] != '443') {
			$socket_import = socket_import_stream(self::$_masterSocket); //not support SSL connections.
			socket_set_option($socket_import, SOL_SOCKET, SO_KEEPALIVE, 1);
			socket_set_option($socket_import, SOL_TCP, TCP_NODELAY, 1);
		}
		//使用非阻塞
		stream_set_blocking(self::$_masterSocket, 0);

		//启动子进程
		for ($i = 0; $i < self::$config['common']['slave_child_num']; $i++)
			self::_forkInstance($i);

		//主进程启动完成
		self::_masterProcessComplete();
	}

	/**
	 * 停止服务
	 */
	private static function _commandStop()
	{
		Log::write('Service stop');

		$master_process_name = self::$config['common']['master_process_name'];
		$slave_process_name = self::$config['common']['slave_process_name'];
		self::checkMasterIsAlive() && exec("ps -ef | grep $master_process_name | grep -v grep | awk '{print $2}' | xargs kill ", $output);
		self::checkSlaveIsAlive() && exec("ps -ef | grep $slave_process_name | grep -v grep | awk '{print $2}' | xargs kill ", $output);

		$pid_file = self::$config['common']['master_pid_file'];
		is_file($pid_file) && unlink($pid_file);
		Func::echoColorText("Server stop\033[0m  \033[0;31;1m[OK]");
	}

	/**
	 * 重启服务
	 */
	private static function _commandRestart()
	{
		Log::write('Service restart ...');
		Func::echoColorText("Service restart ...");
		self::_commandStop();
		self::_commandStart();
	}

	/**
	 * 状态查看
	 */
	private static function _commandStatus()
	{
		Func::echoColorText("Service status ...");

		$master_is_alive = self::checkMasterIsAlive();
		$slave_is_alive = self::checkSlaveIsAlive();

		$stdout = "";
		$stdout .= "\033[44;37mServer Status\033[0m\n";
		if ($master_is_alive) {
			$stdout .= "\033[32mMaster process is runing\033[0m\n";
			$stdout .= $master_is_alive . "\n";
		} else {
			$stdout .= "\033[31mMaster process is not runing\033[0m\n";
			$stdout .= "\n";
		}

		if ($slave_is_alive) {
			$stdout .= "\033[32mSlave process is runing\033[0m\n";
			$stdout .= $slave_is_alive . "\n";
		} else {
			$stdout .= "\033[31mSlave process is not runing\033[0m\n";
			$stdout .= "\n";
		}

		echo $stdout;
	}

	/**
	 * 保存Server主进程的Pid
	 */
	private static function _saveMasterPid()
	{
		$_masterPid = posix_getpid();
		Log::write("_saveMasterPid($_masterPid)");
		$pid_file = self::$config['common']['master_pid_file'];
		if (false === file_put_contents($pid_file, $_masterPid))
			Log::write('Can\'t write pid to masterPid file ', 'FATAL');
	}

	/**
	 * 检查是否在运行
	 * @return boolean
	 */
	private static function checkMasterIsAlive()
	{
		$pid_file = self::$config['common']['master_pid_file'];
		$master_process_name = self::$config['common']['master_process_name'];
		$output = [];
		exec("ps -ef | grep $master_process_name | grep -v grep", $output);
		if (empty($output)) {
			if (file_exists($pid_file))
				unlink($pid_file);
			return false;
		} else {
			return $output[0];
		}
	}

	/**
	 * 检查是否在运行
	 * @return boolean
	 */
	private static function checkSlaveIsAlive()
	{
		$slave_process_name = self::$config['common']['slave_process_name'];
		$output = [];
		exec('ps -ef | grep ' . $slave_process_name . ' | grep -v grep', $output);
		if (empty($output)) {
			return false;
		} else {
			return implode("\n", $output);
		}
	}

	/**
	 * 创建子进程
	 * @param $i 一个Server的实例id
	 */
	private static function _forkInstance($i)
	{
		Log::write("Fork Instance : $i");
		//创建子进程
		$pid = pcntl_fork();
		if ($pid > 0) {
			//如果是主进程
			return true;
		} elseif ($pid === 0) {
			//如果是子进程
			$slave_process_name = self::$config['common']['slave_process_name'];
			Func::setProcessTitle($slave_process_name . $i);
			self::_slave_run($i);
			exit(250);
		} else {
			//创建进程失败
			Log::write('fork child process failed', 'FATAL');
		}
	}

	/**
	 * _slave_run
	 * @param type $i 子进程编号
	 */
	private static function _slave_run($i)
	{
		$base = event_base_new();
		$event = event_new();
		event_set($event, self::$_masterSocket, EV_READ | EV_PERSIST, 'self::http_accept', [$base, $i]); //EV_PERSIST 表示事件是持久的
		event_base_set($event, $base);
		event_add($event);

		event_base_loop($base);
	}

	/**
	 * http_accept
	 * @staticvar int $fid
	 * @param type $socket
	 * @param type $flag
	 * @param type $arr
	 */
	private static function http_accept($socket, $flag, $arr)
	{
		static $fid = 0; //连接ID

		$base = $arr[0];
		$pid = $arr[1]; //子进程ID

		$peername = '';
		$resConnection = stream_socket_accept($socket);
		if ($resConnection == false) {
			Log::write("--->stream_socket_accept err<---", 'INFO');
			self::_forkInstance($pid);
			exit(255);
		}
		stream_set_blocking($resConnection, 0);
		// Compatible with hhvm
		if (function_exists('stream_set_read_buffer')) {
			stream_set_read_buffer($resConnection, 0);
		}
		list($accept_ip, $accept_port) = explode(':', stream_socket_get_name($resConnection, true));
		Log::write("new connection \$pid:$pid \$fid:$fid accept_ip:$accept_ip \$accept_port:$accept_port");

		//注册缓冲区读
		$buffer = event_buffer_new($resConnection, 'self::http_read', 'self::http_write', 'self::http_error', [$base, $fid, $pid, $resConnection]);
		if ($buffer == false)
			Log::write('Error for create new buffered event', 'FATAL');
		event_buffer_base_set($buffer, $base);
		$time_out = self::$config['http']['keep_alive_time_out'];
		event_buffer_timeout_set($buffer, $time_out, $time_out);
		event_buffer_watermark_set($buffer, EV_READ, 0, 0xffffff);
		event_buffer_watermark_set($buffer, EV_WRITE, 0, 0xffffff);
		event_buffer_priority_set($buffer, 60); //超时自动断开时间
		//EV_PERSIST: 表示事件是持久的
		//EV_READ: FD就绪,可以读取的时候,事件成为激活状态
		event_buffer_enable($buffer, EV_READ | EV_PERSIST);

		//保存
		$GLOBALS['hq_connections'][$fid] = $resConnection;
		$GLOBALS['hq_buffers'][$fid] = $buffer;

		$fid++;
	}

	/**
	 * http_read
	 * @param type $buffer
	 * @param type $arr
	 */
	private static function http_read($buffer, $arr)
	{
		//连接ID
		$fid = $arr[1];
		//子进程ID
		$pid = $arr[2];

		//注册一个退出函数.在任何退出的情况下检测是否由于错误引发的.包括die,exit等都会触发
		register_shutdown_function(function () use ($fid, $pid) {
			Log::write("--->shutdown \$pid:$pid \$fid:$fid <---", 'INFO');
		});

		//注册一个错误回调函数
		set_error_handler(function()use ($fid, $pid) {
			$backtrace = debug_backtrace();
			Log::write("--->error_handler \$pid:$pid \$fid:$fid \$backtrace:" . print_r($backtrace[0], true) . "<---", 'INFO');
		});

		stream_socket_enable_crypto($arr[3], true, STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER);
		$request_string = '';
		while ($read = event_buffer_read($buffer, 1024))
			$request_string .= $read;

		//Log::write($request_string);

		$http_response = <<<EOF
HTTP/1.1 200 OK
Server: lxc-blog/1.0 (PHP)
Content-Type: text/html;charset=utf-8
Connection: keep-alive
Content-Length: 7

It's ok
EOF;
		event_buffer_write($GLOBALS['hq_buffers'][$fid], $http_response, strlen($http_response));
	}

	/**
	 * http_write
	 * @param type $buffer
	 * @param type $error
	 * @param type $arr
	 */
	private static function http_write($buffer, $arr)
	{
		$fid = $arr[1]; //连接ID
		$pid = $arr[2]; //子进程ID
		Log::write("http_write \$pid:$pid \$fid:$fid ", 'INFO');
	}

	/**
	 * http_error
	 */
	private static function http_error($buffer, $error, $arr)
	{
		$fid = $arr[1]; //连接ID
		$pid = $arr[2]; //子进程ID
		//关闭用户连接
		event_buffer_disable($buffer, EV_READ | EV_WRITE);
		event_buffer_free($buffer);
		stream_socket_shutdown($GLOBALS['hq_connections'][$fid], STREAM_SHUT_WR);
		fclose($GLOBALS['hq_connections'][$fid]);
		//释放资源
		unset($buffer, $GLOBALS['hq_connections'][$fid], $GLOBALS['hq_buffers'][$fid]);

		Log::write("connection close : \$pid:$pid \$fid:$fid");
	}

	/**
	 * 主进程启动完成
	 */
	private static function _masterProcessComplete()
	{
		Func::echoColorText("Server start\033[0m \033[0;31;1m[OK]");

		//后台执行
		self::_daemon();

		//给主进程起个名字
		Func::setProcessTitle(self::$config['common']['master_process_name']);
		//保存主进程pid
		self::_saveMasterPid();

		#加入timer事件
		$base = event_base_new();
		$tv_value = 1000000 * 1; //秒
		$ev_timer = event_timer_new();
		event_timer_set($ev_timer, 'self::admin_timer_fun', array($ev_timer, $tv_value));
		event_base_set($ev_timer, $base);
		event_timer_add($ev_timer, $tv_value);

		event_base_loop($base);
	}

	private static function admin_timer_fun($socket, $flag, $arr)
	{
		//Log::write(date('Y-m-d H:i:s'));
		event_timer_add($arr[0], $arr[1]);
	}

	/**
	 * 已守护进程的方式启动Server
	 */
	private static function _daemon()
	{
		//文件掩码清0
		umask(0);
		//创建一个子进程
		$pid = pcntl_fork();
		//fork失败
		if ($pid === -1) {
			Log::write('_daemon: fork failed', 'FATAL');
			//父进程
		} else if ($pid > 0) {
			exit();
		}
		//设置子进程为Session leader, 可以脱离终端工作.这是实现daemon的基础
		if (posix_setsid() === -1) {
			Log::write('_daemon: set sid failed', 'FATAL');
		}
		//再次在开启一个子进程
		//这不是必须的,但通常都这么做,防止获得控制终端.
		$pid = pcntl_fork();
		if ($pid === -1) {
			Log::write('_daemon: fork2 failed', 'FATAL');
		} else if ($pid > 0) {
			//将父进程退出
			exit();
		} else {
			//新主进程ID
			//$_masterPid = posix_getpid();
		}
	}

}

Server::start();
